/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package thirdProject;

import thirdProject.GraphUtility;
import framework.Action;
import framework.Percept;
import java.awt.Point;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.Set;
import org.jgrapht.graph.DefaultWeightedEdge;
import org.jgrapht.graph.SimpleDirectedWeightedGraph;
import vacuumAgent.VAAction;
import vacuumAgent.VAAgent;
import vacuumAgent.VAPercept;
import vacuumAgent.VATile.VATileStatus;

/**
 *
 * @author asculx
 */
public class ThirdAgent extends VAAgent {

    enum direction {

        DX, SX, DW, UP, NO
    };
    GraphUtility utilsGraph;
    int testConter = 0;
    Point currPositionToHome;
    Point homePosition;
    int countedRow = 1;
    int countedCol = 1;
    int minValRow = 0;
    int maxValRow = 0;
    int minValCol = 0;
    int maxValCol = 0;
    SimpleDirectedWeightedGraph<Point, DefaultWeightedEdge> blockGraph = null;
    SimpleDirectedWeightedGraph<Point, DefaultWeightedEdge> tileGraph = null;
    ArrayList<direction> mosse;
    ArrayList<direction> mosseCalcolate;
    ArrayList<Point> homePath = null;
    ArrayList<Point> visitedVertex = null;
    ArrayList<Point> existingBlocks = null;
    direction direc = direction.NO;
    Point currVertex;
    Point north;
    Point south;
    Point east;
    Point west;
    boolean isComplete = false;
    int floorDimension = 0;
    int lato = 0;
    int xCurr = 0, yCurr = 0;
    boolean firstStep = true;
    String debugMessage;
    boolean debug = false;

    public ThirdAgent(int energy) {
        super(energy);
        utilsGraph=new GraphUtility();
        
        tileGraph = new SimpleDirectedWeightedGraph<>(DefaultWeightedEdge.class);
        blockGraph = new SimpleDirectedWeightedGraph<>(DefaultWeightedEdge.class);

        
        
        tileGraph.addVertex(new Point(0, 0));
        currVertex = new Point(0, 0);
        currPositionToHome = new Point(0, 0);
        homePosition = new Point(0, 0);

        System.out.println("DIMENSIONE DEL GRAFO" + tileGraph.vertexSet().size());
        mosse = new ArrayList<>();
        mosseCalcolate = new ArrayList<>();

        visitedVertex = new ArrayList<>();
        existingBlocks = new ArrayList<>();
    }

    // ### AGGIUNTO PER OVVIARE AL PROBLEMA DELLA BUSSOLA RUOTATA
    private boolean isNordFree(VAPercept percept) {
        return percept.getNeighborhood().northIsFree();
    }

    private boolean isSouthFree(VAPercept percept) {
        return percept.getNeighborhood().southIsFree();
//        return percept.getNeighborhood().westIsFree();
    }

    private boolean isEastFree(VAPercept percept) {
        return percept.getNeighborhood().eastIsFree();
//        return percept.getNeighborhood().northIsFree();
    }

    private boolean isWestFree(VAPercept percept) {
        return percept.getNeighborhood().westIsFree();
//        return percept.getNeighborhood().southIsFree();
    }

    private void stepNord() {
        xCurr = north.x;
        yCurr = north.y;
        direc = direction.UP;
        addVisited(north);
        currPositionToHome = north;
        currVertex = currPositionToHome;

    }

    private void stepSouth() {
        xCurr = south.x;
        yCurr = south.y;
        direc = direction.DW;
        addVisited(south);
        currPositionToHome = south;
        currVertex = currPositionToHome;
    }

    private void stepEast() {
        xCurr = east.x;
        yCurr = east.y;
        direc = direction.DX;
        addVisited(east);
        currPositionToHome = east;
        currVertex = currPositionToHome;
    }

    private void stepWest() {
        xCurr = west.x;
        yCurr = west.y;
        direc = direction.SX;
        addVisited(west);
        currPositionToHome = west;
        currVertex = currPositionToHome;
    }

    private VAAction actionNord() {
        VAAction a = new VAAction(VAAction.VAActionType.MOVENORTH);
        return a;
    }

    private VAAction actionSouth() {
        VAAction a = new VAAction(VAAction.VAActionType.MOVESOUTH);
        return a;
    }

    private VAAction actionEast() {
        VAAction a = new VAAction(VAAction.VAActionType.MOVEEAST);
        return a;
    }

    private VAAction actionWest() {
        VAAction a = new VAAction(VAAction.VAActionType.MOVEWEST);
        return a;
    }

    private void setNeightboor(int xCurr, int yCurr) {
        //NOTA : LA BUSSOLA é RUOTATA DI 90° GRADI IN SENSO antiORARIO OVVERO IL NORD E VERSO DESTRA
        currVertex = new Point(xCurr, yCurr);
        north = new Point(xCurr - 1, yCurr);
        south = new Point(xCurr + 1, yCurr);
        east = new Point(xCurr, yCurr + 1);
        west = new Point(xCurr, yCurr - 1);
    }
    // ### AGGIUNTO PER OVVIARE AL PROBLEMA DELLA BUSSOLA RUOTATA

    @Override
    public Action execute(Percept percept) {
        VAPercept perception = (VAPercept) percept;
        VAAction action = new VAAction(VAAction.VAActionType.NOOP);
        if (firstStep) {
            lato = perception.getFloor().getSize();
            floorDimension = lato * lato;
            firstStep = false;
        }
        prepareSettings(perception);

        if (!iAmAtHome) {
            if (!isCompleteMap() && !isComplete) {
                if (perception.getCurrentTileStatus() == VATileStatus.DIRTY) {
                    action.setActionType(VAAction.VAActionType.SUCK);
                } else {
                    action = defaultmove(perception);
                }
                countRowsAndCols();
                adjustBlocks();
            } else if (perception.getCurrentTileStatus() == VATileStatus.DIRTY) {
                action.setActionType(VAAction.VAActionType.SUCK);
            } else {
                action = goHome();
            }
        }
        return action;
    }

    private void prepareSettings(VAPercept percept) {
        setNeightboor(xCurr, yCurr);
        isBlock(percept);
        addVertexs(percept);
    }

    private VAAction defaultmove(VAPercept percept) {
        VAAction action = new VAAction(VAAction.VAActionType.NOOP);
        boolean noop = false;
//        prepareSettings(percept);
        direc = direction.NO;
        action = normalActionDefault(percept);
        noop = action.getActionType().equals(VAAction.VAActionType.NOOP);

        if (noop) {
            action = computeAction();
        } else {
            mosseCalcolate.clear();
            mosse.add(direc);
        }
        return action;
    }

    private VAAction normalActionDefault(VAPercept percept) {
        VAAction action = new VAAction(VAAction.VAActionType.NOOP);

        if (isNordFree(percept) && !visitedVertex.contains(north)) {
            action = actionNord();
            stepNord();
        } else if (isWestFree(percept) && !visitedVertex.contains(west)) {
            action = actionWest();
            stepWest();
        } else if (isEastFree(percept) && !visitedVertex.contains(east)) {
            action = actionEast();
            stepEast();
        } else if (isSouthFree(percept) && !visitedVertex.contains(south)) {
            action = actionSouth();
            stepSouth();
        } else {
            debugMessage = "NOOPERATION FACILE xcurr= " + xCurr + " yCurr = " + yCurr;
        }
        return action;
    }

    private VAAction computeAction() {
        VAAction a = new VAAction(VAAction.VAActionType.NOOP);
        direc = direction.NO;
        ArrayList<Point> path2;
        double percPAv = (((double) tileGraph.vertexSet().size() / (double) floorDimension) * 100);
        double percVis = (((double) visitedVertex.size() / (double) tileGraph.vertexSet().size()) * 100);

        if ((mosseCalcolate.isEmpty()) && percPAv > 50 && percVis > 70) {
            path2 = utilsGraph.pathTofirstUnvisited(tileGraph, currVertex, visitedVertex);
            mosseCalcolate = convertList(path2);
//            direc = mosseCalcolate.remove(mosseCalcolate.size() - 1);
            direc = mosseCalcolate.remove(0);
            a = stepComputedAction();
            mosse.add(direc);
        } else if (!mosseCalcolate.isEmpty()) {
            System.out.println("EXECUTE MOSSE CALCOLATE ");
            System.out.println("MOSSE CALCOLATE " + mosseCalcolate);
//            direc = mosseCalcolate.remove(mosseCalcolate.size() - 1);
            direc = mosseCalcolate.remove(0);
            a = stepComputedAction();
            mosse.add(direc);
        } else if (!mosse.isEmpty()) {
            direc = mosse.remove(mosse.size() - 1);
            a = undoStep();
        }
        return a;
    }

    
     private ArrayList<direction> convertList(ArrayList<Point> origList) {
        ArrayList<direction> retList = new ArrayList<>();
        direction d = direction.NO;
        Point curr = currPositionToHome;
        Point succ = new Point(); 
        if(!origList.isEmpty()){
            origList.remove(origList.size()-1);
        }
        
        while(!origList.isEmpty()){
            d=direction.NO;
            succ=origList.remove(origList.size()-1);
            if (succ.y < curr.y) {
                d = direction.SX;
            } else if (succ.y > curr.y) {
                d = direction.DX;
            } else if (succ.x > curr.x) {
                d = direction.DW;
            } else if (succ.x < curr.x) {
                d = direction.UP;
            }
            retList.add(d);
            curr = succ;
        }
        System.out.println("PERCORSO OTTIMIZZATO DA SEGUIRE  = " + retList);
        return retList;
    }
    
    
//    private ArrayList<direction> convertList(ArrayList<Point> origList) {
//        ArrayList<direction> retList = new ArrayList<>();
//        direction d = direction.NO;
//        Point curr = currPositionToHome;
//        Point succ = new Point();
//        for (Iterator<Point> it = origList.iterator(); it.hasNext();) {
//            succ = it.next();
//            
//            if (succ.y < curr.y) {
//                d = direction.DX;
//            } else if (succ.y > curr.y) {
//                d = direction.SX;
//            } else if (succ.x > curr.x) {
//                d = direction.UP;
//            } else if (succ.x < curr.y) {
//                d = direction.DW;
//            }
//            retList.add(d);
//            curr = succ;
//        }
//        System.out.println("PERCORSO OTTIMIZZATO DA SEGUIRE  = " + retList);
//        return retList;
//    }

    private VAAction stepComputedAction() {
        System.out.println("AZIONE MOSSE CALCOLATE ");
        VAAction a = new VAAction(VAAction.VAActionType.NOOP);
        if (direc == direction.DW) {
            a = actionSouth();
            stepSouth();
        } else if (direc == direction.UP) {
            a = actionNord();
            stepNord();
        } else if (direc == direction.SX) {
            a = actionWest();
            stepWest();
        } else if (direc == direction.DX) {
            a = actionEast();
            stepEast();
        }
        return a;
    }
    private boolean iAmAtHome = false;

    private VAAction goHome() {
        VAAction a = new VAAction(VAAction.VAActionType.NOOP);
        if (homePath == null) {
            System.out.println("GO_HOME METHOD");
            homePath = utilsGraph.getMinPath(tileGraph, currVertex, homePosition);
            if (!homePath.isEmpty()) {
                homePath.remove(0);
            }
        }

        if (!currPositionToHome.equals(homePosition) && (!homePath.isEmpty())) {
            Point p = homePath.remove(0);
            a = goHomeAction(p);
            currPositionToHome = p;
            currVertex = currPositionToHome;
        }
        if (homePath.isEmpty() && !iAmAtHome) {
            isComplete = true;
            iAmAtHome = true;
        }
        return a;
    }

    private VAAction goHomeAction(Point p) {
        VAAction a = new VAAction(VAAction.VAActionType.NOOP);
        if (p.y < currPositionToHome.y) {
            a = actionWest();
            stepWest();
        } else if (p.y > currPositionToHome.y) {
            a = actionEast();
            stepEast();
        } else if (p.x > currPositionToHome.x) {
            a = actionSouth();
            stepSouth();
        } else if (p.x < currPositionToHome.x) {
            a = actionNord();
            stepNord();
        }
        return a;
    }

    private VAAction undoStep() {
        VAAction a = new VAAction(VAAction.VAActionType.NOOP);
        if (direc == direction.DW) {
            a = actionNord();
            stepNord();
        } else if (direc == direction.UP) {
            a = actionSouth();
            stepSouth();
        } else if (direc == direction.DX) {
            a = actionWest();
            stepWest();
        } else if (direc == direction.SX) {
            a = actionEast();
            stepEast();
        }
        return a;
    }

    private void isBlock(VAPercept percept) {

        if (!percept.getNeighborhood().southIsFree() && !blockGraph.containsVertex(south)) {
            blockGraph.addVertex(south);
        }
        if (!percept.getNeighborhood().eastIsFree() && !blockGraph.containsVertex(east)) {
            blockGraph.addVertex(east);
        }
        if (!percept.getNeighborhood().northIsFree() && !blockGraph.containsVertex(north)) {
            blockGraph.addVertex(north);
        }
        if (!percept.getNeighborhood().westIsFree() && !blockGraph.containsVertex(west)) {
            blockGraph.addVertex(west);
        }
    }

    private boolean isTileContains(Point p) {
        return tileGraph.containsVertex(p);
    }

    private void addVertexs(VAPercept percept) {
        currVertex = new Point(xCurr, yCurr);

        if (!isTileContains(currVertex)) {
            tileGraph.addVertex(currVertex);
        }

        if (isNordFree(percept) && !isTileContains(north)) {
            tileGraph.addVertex(north);
            tileGraph.addEdge(currVertex, north);
        } else if (isNordFree(percept) && isTileContains(north)) {
            tileGraph.addEdge(currVertex, north);
        }

        if (isSouthFree(percept) && !isTileContains(south)) {
            tileGraph.addVertex(south);
            tileGraph.addEdge(currVertex, south);
        } else if (isSouthFree(percept) && isTileContains(south)) {
            tileGraph.addEdge(currVertex, south);
        }

        if (isWestFree(percept) && !isTileContains(west)) {
            tileGraph.addVertex(west);
            tileGraph.addEdge(currVertex, west);
        } else if (isWestFree(percept) && isTileContains(west)) {
            tileGraph.addEdge(currVertex, west);
        }

        if (isEastFree(percept) && !isTileContains(east)) {
            tileGraph.addVertex(east);
            tileGraph.addEdge(currVertex, east);
        } else if (isEastFree(percept) && isTileContains(east)) {
            tileGraph.addEdge(currVertex, east);
        }
    }

    private void addVisited(Point toAdd) {
        if (!visitedVertex.contains(toAdd)) {
            visitedVertex.add(toAdd);
        }
    }

    private void adjustBlocks() {
        for (int i = minValRow; i <= maxValRow; i++) {
            for (int j = minValCol; j <= maxValCol; j++) {
                Point p = new Point(i, j);
                if (blockGraph.containsVertex(p) && !existingBlocks.contains(p)) {
                    existingBlocks.add(p);
                    utilsGraph.edgingBlockGraph(blockGraph, p);
                }
            }
        }
        System.out.println("SIZE existing BLOCK : " + existingBlocks.size());
    }

    private void countRowsAndCols() {
        int testX = (Integer) currVertex.x;
        int testY = (Integer) currVertex.y;

        if (testX < minValRow) {
            minValRow = testX;
        } else if (testX > maxValRow) {
            maxValRow = testX;
        } else if (testY < minValCol) {
            minValCol = testY;
        } else if (testY > maxValCol) {
            maxValCol = testY;
        }
        countedRow = Math.abs(maxValRow - minValRow);
        countedCol = Math.abs(maxValCol - minValCol);
    }

    private boolean isCompleteMap() {
        boolean retVar = false;
        retVar = ((visitedVertex.size() + existingBlocks.size()) >= floorDimension);

        if (!retVar && mosseCalcolate.isEmpty() && !hasUnvisited()) {
            retVar = true;
        }
        return retVar;
    }

    private boolean hasUnvisited() {
        Set<Point> tileVertex = tileGraph.vertexSet();
        int visSize = visitedVertex.size();
        int tileSize = tileGraph.vertexSet().size();

        if (tileSize == visSize) {
            return false;
        }

        for (Point point : tileVertex) {
            if (!visitedVertex.contains(point)) {
                return true;
            }
        }
        return false;
    }
}
